/* Example use of Reed-Solomon library 
 *
 * Copyright Henry Minsky (hqm@alum.mit.edu) 1991-2009
 * (Ported to C# by Jake Tober 2010)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. 
 *
 * Commercial licensing is available under a separate license, please
 * contact author for details.
 *
 * This same code demonstrates the use of the encodier and 
 * decoder/error-correction routines. 
 *
 * We are assuming we have at least four bytes of parity (NPAR >= 4).
 * 
 * This gives us the ability to correct up to two errors, or 
 * four erasures. 
 *
 * In general, with E errors, and K erasures, you will need
 * 2E + K bytes of parity to be able to correct the codeword
 * back to recover the original message data.
 *
 * You could say that each error 'consumes' two bytes of the parity,
 * whereas each erasure 'consumes' one byte.
 *
 * Thus, as demonstrated below, we can inject one error (location unknown)
 * and two erasures (with their locations specified) and the 
 * error-correction routine will be able to correct the codeword
 * back to the original message.
 * */
using System;
using System.Collections.Generic;
using System.Linq;
using Logger;
using System.Diagnostics.Contracts;

namespace RSCode
{
    internal static class Example
    {
        private static Log log = Log.Create("Example");

        static void Main()
        {
            ConsoleLogger.Init();
            ConsoleLogger.BlackListFilter("Berlekamp");

            int parity = 2;
            int maxMsgLength = 256 - parity;
            ReedSolomonNet.ReedSolomon rs = new ReedSolomonNet.ReedSolomon(parity);

            String msg1 = truncate("Hello World. Nervously I loaded the twin ducks aboard the revolving platform.", maxMsgLength);
            String msg2 = truncate("From fairest creatures we desire increase, That thereby beauty's rose might never die", maxMsgLength);
            String msg3 = truncate("Marley was dead: to begin with. There is no doubt whatever about that. The register of his burial was signed by the clergyman, the clerk, the undertaker, and the chief mourner. Scrooge signed it: and Scrooge's name was good upon 'Change, for anything he chose to put his hand to.", maxMsgLength);
            
            byte[] codeword1, codeword2, codeword3, output1, output2, output3;
            int[] erasures1, erasures2, erasures3;

            codeword1 = rs.Encode(bytes(msg1));
            codeword2 = rs.Encode(bytes(msg2));
            codeword3 = rs.Encode(bytes(msg3));

            erasures1 = randomErrors(codeword1, parity);
            erasures2 = randomErrors(codeword2, parity);
            erasures3 = randomErrors(codeword3, parity);

            output1 = rs.Decode(codeword1, erasures1);
            output2 = rs.Decode(codeword2, erasures2);
            output3 = rs.Decode(codeword3, erasures3);

            log.Debug("Msg#1");
            log.Debug("Helper: " + tryParse(codeword1));
            log.Debug("Helper: " + tryParse(output1));
            log.Debug("Msg#2");
            log.Debug("Helper: " + tryParse(codeword2));
            log.Debug("Helper: " + tryParse(output2));
            log.Debug("Msg#3");
            log.Debug("Helper: " + tryParse(codeword3));
            log.Debug("Helper: " + tryParse(output3));
            
        }

        static String tryParse(byte[] text)
        {
            Contract.Requires(text != null);
            Contract.Ensures(Contract.Result<String>() != null);

            String result = "???";
            try
            {
                byte[] copy = new byte[text.Length];
                for (int i = 0; i < text.Length; i++)
                {
                    copy[i] = text[i] != 0 ? text[i] : (byte)32;
                }

                result = System.Text.Encoding.UTF8.GetString(copy);
            }
            catch { }
            return result;
        }

        private static byte[] bytes(String text)
        {
            Contract.Requires(text != null);
            Contract.Ensures(Contract.Result<byte[]>() != null);

            return System.Text.Encoding.UTF8.GetBytes(text); 
        }

        private static int[] randomErrors(byte[] dst, int numberOfErrors)
        {
            Contract.Requires(dst != null && dst.Length >= numberOfErrors);
            Contract.Requires(numberOfErrors >= 0);
            Contract.Ensures(Contract.Result<int[]>() != null && Contract.Result<int[]>().Length == numberOfErrors);

            Random r = new Random();
            int[] result = new int[numberOfErrors];
            for (int i = 0; i < numberOfErrors; i++)
            {
                int pos = -1;
                while (pos == -1)
                {
                    pos = r.Next(0, dst.Length);
                    for (int j = 0; j < i; j++)
                    {
                        if (result[j] == pos)
                        { pos = -1; }
                    }
                }
                dst[pos] = 0;
                result[i] = pos;
            }
            return result;
        }
        private static String truncate(String text, int length)
        {
            Contract.Requires(text != null);
            Contract.Requires(length > 0);
            Contract.Ensures(Contract.Result<String>() != null);
            Contract.Ensures(Contract.Result<String>().Length <= length);

            return text.Length > length ? text.Substring(0, length) : text;
        }
    }
}